﻿using common.libs;
using common.libs.extends;
using common.server.model;
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Threading.Tasks;

namespace common.server
{
    public class MessengerSender
    {
        private NumberSpace requestIdNumberSpace = new NumberSpace(0);
        private WheelTimer<TimeoutState> wheelTimer = new WheelTimer<TimeoutState>();
        private ConcurrentDictionary<ulong, WheelTimerTimeout<TimeoutState>> sends = new ConcurrentDictionary<ulong, WheelTimerTimeout<TimeoutState>>();
        private SimpleObjectPool<MessageRequestWrap> messageRequestWrapPool = new SimpleObjectPool<MessageRequestWrap>();

        public MessengerSender()
        {
        }

        public async Task<MessageResponeInfo> SendReply<T>(MessageRequestParamsInfo<T> msg)
        {
            if (msg.RequestId == 0)
            {
                msg.RequestId = requestIdNumberSpace.Get();
            }
            WheelTimerTimeout<TimeoutState> timeout = NewReply(msg.RequestId, msg.Timeout);
            if (!await SendOnly(msg).ConfigureAwait(false))
            {
                sends.TryRemove(msg.RequestId, out _);
                timeout.Cancel();
                timeout.Task.State.Tcs.SetResult(new MessageResponeInfo { Code = MessageResponeCodes.NOT_CONNECT });
            }
            return await timeout.Task.State.Tcs.Task.ConfigureAwait(false);
        }
        public async Task<bool> SendOnly<T>(MessageRequestParamsInfo<T> msg)
        {
            try
            {
                if (msg.RequestId == 0)
                {
                    msg.RequestId = requestIdNumberSpace.Get();
                }
                if (msg.Connection == null)
                {
                    return false;
                }

                MessageRequestWrap wrap = messageRequestWrapPool.Rent();
                wrap.Content = msg.Connection.EncodeEnabled ? msg.Connection.Crypto.Encode(msg.Data.ToBytes()) : msg.Data.ToBytes();
                wrap.RequestId = msg.RequestId;
                wrap.Path = msg.MemoryPath;
                byte[] bytes = wrap.ToArray(msg.Connection.ServerType);

                bool res = await msg.Connection.Send(bytes).ConfigureAwait(false);

                wrap.Reset();
                messageRequestWrapPool.Return(wrap);

                if (res & msg.Connection.ServerType == ServerType.UDP)
                {
                    msg.Connection.UpdateTime(DateTimeHelper.GetTimeStamp());
                }
                return res;
            }
            catch (Exception ex)
            {
                Logger.Instance.Error(ex);
            }
            return false;
        }
        public async ValueTask ReplyOnly(MessageResponseParamsInfo msg)
        {
            try
            {
                MessageResponseWrap wrap = new MessageResponseWrap
                {
                    RequestId = msg.RequestId,
                    Code = msg.Code,
                    Content = msg.Connection.EncodeEnabled ? msg.Connection.Crypto.Encode(msg.Data.ToBytes()) : msg.Data.ToBytes()
                };
                byte[] bytes = wrap.ToArray(msg.Connection.ServerType);

                bool res = await msg.Connection.Send(bytes).ConfigureAwait(false);

                if (res & msg.Connection.ServerType == ServerType.UDP)
                {
                    msg.Connection.UpdateTime(DateTimeHelper.GetTimeStamp());
                }
            }
            catch (Exception ex)
            {
                Logger.Instance.Debug(ex);
            }
        }

        public void Response(MessageResponseWrap wrap)
        {
            if (sends.TryRemove(wrap.RequestId, out WheelTimerTimeout<TimeoutState> timeout))
            {
                timeout.Cancel();
                timeout.Task.State.Tcs.SetResult(new MessageResponeInfo { Code = wrap.Code, Data = wrap.Memory });
            }
        }

        private WheelTimerTimeout<TimeoutState> NewReply(ulong requestId, int timeoutDelay = 15000)
        {
            if (timeoutDelay <= 0)
            {
                timeoutDelay = 15000;
            }

            WheelTimerTimeout<TimeoutState> timeout = wheelTimer.NewTimeout(new WheelTimerTimeoutTask<TimeoutState>
            {
                Callback = TimeoutCallback,
                State = new TimeoutState { RequestId = requestId, Tcs = new TaskCompletionSource<MessageResponeInfo>() }
            }, timeoutDelay);
            sends.TryAdd(requestId, timeout);
            return timeout;
        }
        private void TimeoutCallback(WheelTimerTimeout<TimeoutState> timeout)
        {
            sends.TryRemove(timeout.Task.State.RequestId, out _);
            timeout.Task.State.Tcs.SetResult(new MessageResponeInfo { Code = MessageResponeCodes.TIMEOUT });
        }
    }

    public class MessageResponeInfo
    {
        public MessageResponeCodes Code { get; set; } = MessageResponeCodes.OK;
        public ReadOnlyMemory<byte> Data { get; set; } = Array.Empty<byte>();
    }

    public class TimeoutState
    {
        public ulong RequestId { get; set; }
        public TaskCompletionSource<MessageResponeInfo> Tcs { get; set; }
    }
}

